package com.example.myspringboot.netty;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.slf4j.MDC;
import org.springframework.core.env.Environment;

import java.nio.charset.StandardCharsets;

/**
 * DmNettyServerHandle 类，用于处理 Netty 服务器接收到的客户端消息。
 *
 * @author cy
 * @since 2024/2/20
 */
@EqualsAndHashCode(callSuper = true)
@ChannelHandler.Sharable
public class DmNettyServerHandle extends SimpleChannelInboundHandler<String> {
    /**
     * 日志对象，用于记录程序运行时的日志信息。
     * 使用静态初始化块，在类加载时初始化，确保日志对象的唯一性。
     * 通过LogFactory获取具体的日志实现对象，支持不同的日志实现机制（如Log4j、SLF4J等）。
     */
    private static final Log log = LogFactory.get();

    private static final String LOG_ID_KEY = "logId";

    /**
     * 使用StringBuilder来高效构建字符串。
     * 由于其在字符串操作中具有高性能和线程安全的特性，因此被选择为私有成员变量。
     * 这个StringBuilder实例将被用于累积和构建各种字符串操作的结果，而不是每次操作都创建新的字符串对象，
     * 这样可以减少内存分配和垃圾收集的开销。
     */
    private final StringBuilder sb = new StringBuilder();


    /**
     * 重写 channelRead0 方法，处理 ChannelHandlerContext 中的读事件。
     * 当从客户端接收到新的字符串消息时，此方法会被调用，并将接收到的消息追加到共享的 StringBuffer 对象中。
     *
     * @param ctx ChannelHandlerContext，包含通道的相关上下文信息
     * @param msg String，从客户端接收到的消息
     * @throws Exception 如果在处理过程中发生异常，则抛出异常
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        sb.append(msg);
    }

    /**
     * 当通道读取操作完成时的处理函数。
     * 主要负责解析接收到的数据，处理完成后向客户端发送响应。
     *
     * @param ctx 通道上下文，用于通道的读写操作和关闭操作。
     * @throws Exception 如果读取或写入操作发生异常。
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 解析接收到的数据长度。
        // 获取前8个字符并转换为整数
        String len = StrUtil.str(ArrayUtil.sub(sb.toString().getBytes(StandardCharsets.UTF_8), 0, 8), StandardCharsets.UTF_8);
        int lens = Integer.parseInt(len);

        // 检查是否已经接收到了足够长度的数据。
        // 如果接收到的数据长度大于等于lens，则处理数据
        if (sb.toString().length() >= lens) {
            // 日志记录接收完成的数据。
            log.info("接收完成： {}", sb.toString());
            String str = sb.toString();
            sb.setLength(0);

            // 提取实际的信息内容。
            String info = str.substring(8, 8 + lens);

            // 日志记录处理中的数据。
            // 处理数据
            log.info("数据处理: {}", info);

            // 构造处理后的响应数据。
            String res = info + "数据处理";
            int length = res.getBytes(StandardCharsets.UTF_8).length;
            String lenHead = StrUtil.fillBefore(String.valueOf(length), '0', 8);

            // 日志记录发送的响应数据。
            log.info("返回信息: {}", lenHead + res);

            // 将响应数据写入通道并关闭通道。
            // 将处理后的数据写入channel并关闭channel
            ChannelFuture writeAndFlush = ctx.writeAndFlush(lenHead + res);
            writeAndFlush.addListener((future) -> {
                // 如果写入操作失败，记录日志。
                if (!future.isSuccess()) {
                    log.error("Failed to write message back to the client", future.cause());
                }
                // 关闭通道。
                ctx.close();
            });
            log.info("success return info");

            // 清除日志ID，确保每个连接的日志唯一。
            try {
                MDC.remove(LOG_ID_KEY);
            } catch (IllegalArgumentException e) {
                log.warn(e.getMessage(), e);
            }

            // 再次关闭通道，确保关闭操作被执行。
            ctx.close();
        } else {
            // 如果接收的数据长度不足，清空缓冲区并关闭通道。
            log.info("接收长度错误： {}", sb.toString());
            sb.setLength(0);

            // 将空字符串写入通道并关闭通道。
            // 将处理后的数据写入channel并关闭channel
            ChannelFuture writeAndFlush = ctx.writeAndFlush("");
            writeAndFlush.addListener((future) -> {
                // 如果写入操作失败，记录日志。
                if (!future.isSuccess()) {
                    log.error("Failed to write message back to the client", future.cause());
                }
                // 关闭通道。
                ctx.close();
            });
            ctx.close();
        }
    }


    /**
     * 重写 exceptionCaught 方法，捕获和处理在通道活动期间发生的异常。
     * 在这里，当出现未处理的异常时，会记录错误信息并关闭与客户端的连接。
     *
     * @param ctx   ChannelHandlerContext，包含通道的相关上下文信息
     * @param cause Throwable，捕获到的异常对象
     * @throws Exception 如果在处理过程中发生其他异常，则抛出异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 记录并输出异常信息
        log.error("Netty Server caught an exception: {}", cause.getMessage(), cause);
        // 关闭与客户端的连接
        ctx.close();
    }
}
